using System;
using System.Collections.Generic;
using System.Net;
using System.Net.NetworkInformation;
using System.Net.Sockets;
using ServiceStack.Logging;

namespace ServiceStack.Common.Extensions
{
	/// <summary>
	/// Useful IPAddressExtensions from: 
	/// http://blogs.msdn.com/knom/archive/2008/12/31/ip-address-calculations-with-c-subnetmasks-networks.aspx
	/// 
	/// </summary>
	public static class IPAddressExtensions
	{
		private static ILog log = LogManager.GetLogger(typeof (IPAddressExtensions));

		public static IPAddress GetBroadcastAddress(this IPAddress address, IPAddress subnetMask)
		{
			var ipAdressBytes = address.GetAddressBytes();
			var subnetMaskBytes = subnetMask.GetAddressBytes();

			if (ipAdressBytes.Length != subnetMaskBytes.Length)
				throw new ArgumentException("Lengths of IP address and subnet mask do not match.");

			var broadcastAddress = new byte[ipAdressBytes.Length];
			for (var i = 0; i < broadcastAddress.Length; i++)
			{
				broadcastAddress[i] = (byte)(ipAdressBytes[i] | (subnetMaskBytes[i] ^ 255));
			}
			return new IPAddress(broadcastAddress);
		}
        
		public static IPAddress GetNetworkAddress(this IPAddress address, IPAddress subnetMask)
		{
			var ipAdressBytes = address.GetAddressBytes();
			var subnetMaskBytes = subnetMask.GetAddressBytes();

			return new IPAddress(GetNetworkAddressBytes(ipAdressBytes, subnetMaskBytes));
		}

		public static byte[] GetNetworkAddressBytes(byte[] ipAdressBytes, byte[] subnetMaskBytes) 
		{
			if (ipAdressBytes.Length != subnetMaskBytes.Length)
				throw new ArgumentException("Lengths of IP address and subnet mask do not match.");

			var broadcastAddress = new byte[ipAdressBytes.Length];
			for (var i = 0; i < broadcastAddress.Length; i++)
			{
				broadcastAddress[i] = (byte)(ipAdressBytes[i] & (subnetMaskBytes[i]));
			}
			return broadcastAddress;
		}

		public static bool IsInSameIpv6Subnet(this IPAddress address2, IPAddress address)
		{
			if (address2.AddressFamily != AddressFamily.InterNetworkV6 || address.AddressFamily != AddressFamily.InterNetworkV6)
			{
				throw new ArgumentException("Both IPAddress must be IPV6 addresses");
			}
			var address1Bytes = address.GetAddressBytes();
			var address2Bytes = address2.GetAddressBytes();

			return IsInSameIpv6Subnet(address1Bytes, address2Bytes);
		}

		public static bool IsInSameIpv6Subnet(this byte[] address1Bytes, byte[] address2Bytes) 
		{
			if (address1Bytes.Length != address2Bytes.Length)
				throw new ArgumentException("Lengths of IP addresses do not match.");

			for (var i = 0; i < 8; i++)
			{
				if (address1Bytes[i] != address2Bytes[i])
				{
					return false;
				}
			}

			return true;
		}

		public static bool IsInSameIpv4Subnet(this IPAddress address2, IPAddress address, IPAddress subnetMask)
		{
			if (address2.AddressFamily != AddressFamily.InterNetwork || address.AddressFamily != AddressFamily.InterNetwork)
			{
				throw new ArgumentException("Both IPAddress must be IPV4 addresses");
			}
			var network1 = address.GetNetworkAddress(subnetMask);
			var network2 = address2.GetNetworkAddress(subnetMask);

			return network1.Equals(network2);
		}

		public static bool IsInSameIpv4Subnet(this byte[] address1Bytes, byte[] address2Bytes, byte[] subnetMaskBytes)
		{
			if (address1Bytes.Length != address2Bytes.Length)
				throw new ArgumentException("Lengths of IP addresses do not match.");

			var network1Bytes = GetNetworkAddressBytes(address1Bytes, subnetMaskBytes);
			var network2Bytes = GetNetworkAddressBytes(address2Bytes, subnetMaskBytes);

			return network1Bytes.AreEqual(network2Bytes);
		}


		/// <summary>
		/// Gets the ipv4 addresses from all Network Interfaces that have Subnet masks.
		/// </summary>
		/// <returns></returns>
		public static Dictionary<IPAddress, IPAddress> GetAllNetworkInterfaceIpv4Addresses()
		{
			var map = new Dictionary<IPAddress, IPAddress>();

			try
			{
				foreach (var ni in NetworkInterface.GetAllNetworkInterfaces())
				{
					foreach (var uipi in ni.GetIPProperties().UnicastAddresses)
					{
						if (uipi.Address.AddressFamily != AddressFamily.InterNetwork) continue;

						if (uipi.IPv4Mask == null) continue; //ignore 127.0.0.1
						map[uipi.Address] = uipi.IPv4Mask;
					}
				}
			}
			catch (NotImplementedException ex)
			{
				log.Warn("MONO does not support NetworkInterface.GetAllNetworkInterfaces(). Could not detect local ip subnets.", ex);
			} 
			return map;
		}

		/// <summary>
		/// Gets the ipv6 addresses from all Network Interfaces.
		/// </summary>
		/// <returns></returns>
		public static List<IPAddress> GetAllNetworkInterfaceIpv6Addresses()
		{
			var list = new List<IPAddress>();

			try
			{
				foreach (var ni in NetworkInterface.GetAllNetworkInterfaces())
				{
					foreach (var uipi in ni.GetIPProperties().UnicastAddresses)
					{
						if (uipi.Address.AddressFamily != AddressFamily.InterNetworkV6) continue;
						list.Add(uipi.Address);
					}
				}

			}
			catch (NotImplementedException ex)
			{
				log.Warn("MONO does not support NetworkInterface.GetAllNetworkInterfaces(). Could not detect local ip subnets.", ex);
			}
			
			return list;
		}

	}
}